package show.me.the.money.dao;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.springframework.web.context.WebApplicationContext;

import show.me.the.money.accredit.User;
import show.me.the.money.accredit.condition.Condition;
import show.me.the.money.accredit.result.AuthorizeResult;
import show.me.the.money.config.schema.Schema;
import show.me.the.money.config.schema.SchemaItem;
import show.me.the.money.config.schema.SchemaRelation;
import show.me.the.money.dao.exception.DataAccessException;
import show.me.the.money.dao.exception.DeleteDataAccessException;
import show.me.the.money.dao.exception.InsertDataAccessException;
import show.me.the.money.dao.exception.QueryDataAccessException;
import show.me.the.money.dao.exception.UpdateDataAccessException;
import show.me.the.money.keyserver.util.MsockUtil;
import show.me.the.money.util.context.Context;
import show.me.the.money.util.context.HashMapContext;
import show.me.the.money.util.exp.ExpRunner;


@SuppressWarnings("unchecked")
public class SimpleDAO {
	
	private static final Log logger = LogFactory.getLog(SimpleDAO.class);
	
	public final static int AUTHORIZATION_ALL = 1;
	public final static int AUTHORIZATION_IGNORE = 0;
	public final static int UN_PAGED_QUERY_WARNING_LEVEL = 1000;
	public final static String DEFAULT_QUERYCNDSTYPE = "filter";
	
	private Session ss;
	private Transaction trx;
	private Context ctx;
	private Schema sc;
	private User user;
	private AuthorizeCode EntryAuth = new AuthorizeCode();
	private HashMap<String,AuthorizeCode> ItemsAuth = new HashMap<String,AuthorizeCode>();
	public int AUTHORIZATION_MODEL = 1;
	
	public SimpleDAO(Session ss,Schema sc,Context ctx) throws DataAccessException{
		this.ss = ss;
		this.ctx = ctx;
		this.sc = sc;
		init();
	}
	
	public SimpleDAO(Schema sc,Context ctx) throws DataAccessException{
		this.ctx = ctx;
		this.sc = sc;
		init();
	}
	
	private void init() throws DataAccessException{
		if(ctx == null || sc == null){
			throw new DataAccessException("Context or Schema is null");
		}
		if(ss == null){
			try{
				if(ctx.has(Context.DB_SESSION)){
					ss = (Session)ctx.get(Context.DB_SESSION);
				}else{
					WebApplicationContext wac = (WebApplicationContext)ctx.get(Context.APP_CONTEXT);
					SessionFactory sf = (SessionFactory)wac.getBean("mySessionFactory");
					ss = sf.openSession();
					ctx.put(Context.DB_SESSION, ss);
				}
			}
			catch(HibernateException e){
				throw new DataAccessException("OpenDatebaseConnectFailed:" + e.getMessage());
			}
		}
		trx = ss.getTransaction();	// init transaction
		user = (User)ctx.get("user.instance");
		if(AUTHORIZATION_MODEL == AUTHORIZATION_IGNORE){
			EntryAuth.authorizeAll();
		}
		else{
			AuthorizeResult result = user.authorize("storage",sc.getId());
			if(result.getResult() == AuthorizeResult.NO){
				return;
			}
			EntryAuth.parse(result.getAuthorizeValue());
			List<SchemaItem> items = sc.getAllItemsList();
			for(SchemaItem it : items){
				String fId = it.getId();
				if(AUTHORIZATION_MODEL == AUTHORIZATION_ALL){
					result = user.authorize("storage",sc.getId() + "." + fId);
					AuthorizeCode itemAuth = new AuthorizeCode();
					//if(result.getResult() == AuthorizeResult.YES){
						String acValue = result.getAuthorizeValue();
						itemAuth.parse(acValue);
					//}
					ItemsAuth.put(fId,itemAuth);
				}
			}
		}
	}
	
	public void beginTransaction() throws DataAccessException{
		try{
			trx =ss.getTransaction();
			trx.begin();
		}
		catch(HibernateException e){
			throw new DataAccessException("BeginTransactionFailed:" + e.getMessage());
		}
	}
	
	public void commitTransaction() throws DataAccessException{
		if(trx == null){
			return;
		}
		try{
			trx.commit();
		}
		catch(HibernateException e){
			throw new DataAccessException("CommitTransactionFailed:" + e.getMessage());
		}
	}
	
	public void rollbackTransaction() throws DataAccessException{
		if(trx == null){
			return;
		}
		try{
			trx.rollback();
		}
		catch(HibernateException e){
			throw new DataAccessException("RollbackTransactionFailed:" + e.getMessage());
		}
	}
	
	
	public void destroy() throws DataAccessException{
		if(ss == null || !ss.isOpen()){
			return;
		}
		try{
			ss.close();
		}
		catch(HibernateException e){
			throw new DataAccessException("SessionCloseFailed:" + e.getMessage());
		}
	}
	
	public boolean isReady(){
		return !(ss == null || !ss.isOpen() || sc == null || ctx == null);
	}
	
	public HashMap<String,Object> create(HashMap<String,Object> rec) throws InsertDataAccessException{
		if(!isReady()){
			throw new InsertDataAccessException("DAO Not Ready");
		}
		if(AUTHORIZATION_MODEL == AUTHORIZATION_ALL && !EntryAuth.CAN_CREATE){
			throw new InsertDataAccessException("CreateAuthorizeFailed:{schema:" + sc.getId() + ",user:" + user.getId() + "}");
		}
		HashMap<String,Object> GenValues = new HashMap<String,Object>();
		
		Context recCtx = new HashMapContext(rec);
		ctx.putCtx("r", recCtx);
		
		List<SchemaItem> items = sc.getAllItemsList();
		for(SchemaItem it : items){
			String fid = it.getId();
			if(AUTHORIZATION_MODEL == AUTHORIZATION_ALL && !ItemsAuth.get(fid).CAN_CREATE){
				if(it.isRequired()){
					throw new InsertDataAccessException("CreateRequireFieldHasNoAuth:{schema:" + sc.getId() + ",item:" + it.getId() + ",user:" + user.getId() + "}");
				}
				rec.remove(fid);
				continue;
			}
			Object ov = rec.get(fid);
			String v = ov == null ?  "" : String.valueOf(ov);
			if(it.isEvalValue()){
				v = String.valueOf(ExpRunner.run(it.exp(),ctx));
				GenValues.put(fid, v);
			}else if(it.isGenerate()){
				v = MsockUtil.execute(sc.getTableName(),it.getKeyRules(),ctx);
				if(v == null){
					throw new InsertDataAccessException("keyId create failed, please again");
				}
				GenValues.put(fid, v);
			}		
			
			rec.put(fid,it.getTypeValue(v));
		}
		try{
			Serializable pKey = ss.save(sc.getId(),rec);
			GenValues.put(sc.getKey().getId(), pKey);
			return GenValues;
		}
		catch(Exception e){
			throw new InsertDataAccessException(e.getMessage());
		}
	}
	
	
	public HashMap<String,Object> update(HashMap<String,Object> rec) throws UpdateDataAccessException{
		if(!isReady()){
			throw new UpdateDataAccessException("DAO Not Ready");
		}
		String entryName = sc.getId();
		String tableName = sc.getTableName();
		if(AUTHORIZATION_MODEL == AUTHORIZATION_ALL && !EntryAuth.CAN_UPDATE){
			throw new UpdateDataAccessException("UpdateAuthorizeFailed:{schema:" + entryName + ",user:" + user.getId() + "}");
		}
		HashMap<String,Object> GenValues = new HashMap<String,Object>();
		ctx.putCtx("r", new HashMapContext(rec));
		SchemaItem pKeyItem = sc.getKey();
		String pKeyStrVal = String.valueOf(rec.get(pKeyItem.getId()));
		Object pKey = sc.getKey().getTypeValue(pKeyStrVal);
		HashMap<String,Object> o = (HashMap<String,Object>)ss.get(tableName, (java.io.Serializable)pKey);
		if(o == null){
			throw new UpdateDataAccessException("UpdateRecordNotFound:{schema:" + entryName + ",pKey:" + pKeyStrVal + "}");
		}
		List<SchemaItem> items = sc.getAllItemsList();
		for(SchemaItem it : items){
			String fid = it.getId();
			if(AUTHORIZATION_MODEL == AUTHORIZATION_ALL && (!it.canUpdate() || !ItemsAuth.get(fid).CAN_UPDATE)){
				continue;
			}
			Object ov = rec.get(fid);
			String v = ov == null ?  "" : String.valueOf(ov);
			
			if(it.isEvalValue()){
				v = String.valueOf(ExpRunner.run(it.exp(),ctx));
				GenValues.put(fid, v);
			}
			o.put(fid,it.getTypeValue(v));
		}
		
		try{
			ss.update(o);
			ss.flush();
			return GenValues;
		}
		catch(Exception e){
			throw new UpdateDataAccessException(e.getMessage());
		}
	}
	
	public void remove(Object id) throws DeleteDataAccessException{
		if(!isReady()){
			throw new DeleteDataAccessException("DAO Not Ready");
		}
		String entryName = sc.getId();
		if(AUTHORIZATION_MODEL == AUTHORIZATION_ALL && !EntryAuth.CAN_REMOVE){
			throw new DeleteDataAccessException("RemoveAuthorizeFailed:{schema:" + entryName + ",pKey:" + id + "}");
		}
		try{
			SchemaItem key = sc.getKey();
			StringBuffer hql = new StringBuffer("delete from ");
			hql.append(sc.getTableName()).append(" where ").append(key.getId()).append("=");
			if(key.getStaticType() > 6){
				hql.append("'").append(id).append("'");
			}
			else{
				hql.append(id);
			}
			Query q = ss.createQuery(hql.toString());
			int effectCount = q.executeUpdate();
			if(effectCount < 1){
				throw new DeleteDataAccessException("RecordNotFound");
			}
		}
		catch(Exception e){
			throw new DeleteDataAccessException(e.getMessage());
		}
	}
	
	public int removeByFieldValue(String fName,Object v) throws DeleteDataAccessException{
		if(!isReady()){
			throw new DeleteDataAccessException("DAO Not Ready");
		}
		String entryName = sc.getId();
		String tableName = sc.getTableName();
		if(AUTHORIZATION_MODEL == AUTHORIZATION_ALL && !EntryAuth.CAN_REMOVE){
			throw new DeleteDataAccessException("RemoveFailed:{schema:" + entryName + ",fName:" + fName + "}");
		}
		try{
			SchemaItem f = sc.item(fName);
			if(f == null){
				throw new DeleteDataAccessException("LookupFieldNotDefined:{schema:" + entryName + ",fName:" + fName + "}");
			}
			StringBuffer hql = new StringBuffer("delete from ");
			hql.append(tableName).append(" where ").append(fName).append("=");
			if(f.getStaticType() > 6){
				hql.append("'").append(v).append("'");
			}
			else{
				hql.append(v);
			}
			Query q = ss.createQuery(hql.toString());
			int effectCount = q.executeUpdate();
			return effectCount;
		}
		catch(Exception e){
			throw new DeleteDataAccessException(e.getMessage());
		}
	}
	
		
	public HashMap<String,Object> load(Object id)throws QueryDataAccessException{
		if(!isReady()){
			throw new QueryDataAccessException("DAO Not Ready");
		}
		String entryName = sc.getId();
		String tableName = sc.getTableName();
		if(AUTHORIZATION_MODEL == AUTHORIZATION_ALL && !EntryAuth.CAN_READ){
			throw new QueryDataAccessException("LoadFailed:{schema:" + entryName + ",user:" + user.getId() + "}");
		}
		try{
			HashMap<String,Object> rec = (HashMap<String,Object>)ss.get(tableName,(java.io.Serializable)id);
			return rec;
		}
		catch(Exception e){
			throw new QueryDataAccessException("LoadException:" + e.getMessage());
		}
	}
	
	public void marshallRecord(HashMap<String,Object> rec){
		if(rec == null){
			return;
		}
		ctx.putCtx("r", new HashMapContext(rec));
		
		List<SchemaItem> items = sc.getAllItemsList();
		for(SchemaItem it : items){
			String fid = it.getId();
			if(it.isCodedValue()){
				HashMap<String,Object> o = new HashMap<String,Object>();
				Object fv = rec.get(fid);
				o.put("key", fv);
				if(rec.containsKey(fid+"_text")){
					o.put("text", rec.get(fid+"_text"));
				}else{
					o.put("text", it.getDisplayValue(fv));
				}
				rec.put(fid, o);
			}
			if(it.isVirtual() && it.isEvalValue()){
				Object ev = ExpRunner.run(it.exp(),ctx);
				rec.put(fid, ev);
			}
		}
	}
	
	
	public HashMap<String,Object> load(String fName,Object v)throws QueryDataAccessException{
		if(!isReady()){
			throw new QueryDataAccessException("DAO Not Ready");
		}
		String entryName = sc.getId();
		String tableName = sc.getTableName();
		if(AUTHORIZATION_MODEL == AUTHORIZATION_ALL && !EntryAuth.CAN_READ){
			throw new QueryDataAccessException("LoadAuthorizeFailed:{schema:" + entryName + ",user:" + user.getId() + "}");
		}
		try{
			SchemaItem it = sc.item(fName);
			if(it == null){
				throw new QueryDataAccessException("LookupFieldNotDefined:{schema:" + entryName + ",fName:" + fName + "}");
			}
			String fVal = String.valueOf(v);
			if(it.getStaticType() > 6){
				fVal = "'" + fVal + "'";
			}
			StringBuffer hql = new StringBuffer("from ").append(tableName).append(" where ")
				.append(fName).append("=").append(fVal);
			
			Query q = ss.createQuery(hql.toString());
			HashMap<String,Object> rec = (HashMap<String,Object>)q.uniqueResult();
			return rec;
		}
		catch(Exception e){
			throw new QueryDataAccessException("LoadException:" + e.getMessage());
		}
	}
	
	public QueryResult find(List queryCnd) throws QueryDataAccessException{
		return find(queryCnd,1,-1,null,null);
	}
	
	public QueryResult find(List queryCnd,int pageNo,int pageSize,String queryCndsType,String sortInfo) throws QueryDataAccessException{
		if(!isReady()){
			throw new QueryDataAccessException("DAO Not Ready");
		}
		QueryContext qc = new QueryContext();
		initQueryContext(qc,queryCnd,queryCndsType,sortInfo);
		if(qc.getFieldCount() == 0){
			throw new QueryDataAccessException("NoValidFieldForQuery");
		}
		try{
			//query record count
			Date startDt = new Date();
			String hql = qc.buildCountHql();
			Query q = ss.createQuery(hql);
			List ls = q.list();
			int first = (pageNo - 1) * pageSize;
			long totalCount = ((Long)ls.iterator().next()).longValue();			
			if(totalCount == 0  || first > totalCount){
				Date endDt = new Date();
				long cost = endDt.getTime() - startDt.getTime();
				return new QueryResult(totalCount,first,pageSize,hql,cost,null);
			}
			hql = qc.buildQueryHql();
			q = ss.createQuery(hql);
			if(pageSize > 0){
				q.setFirstResult(first);
				q.setMaxResults(pageSize);
			}
			else{
				if(totalCount > UN_PAGED_QUERY_WARNING_LEVEL){
					logger.warn("UnPagedRecordRetrive:{schema:" + sc.getId() + ",totalRecord:" + totalCount +"}\nhql=" + hql);
				}
			}
			List<Object[]> records = q.list();
			Date endDt = new Date();
			long cost = endDt.getTime() - startDt.getTime();
			int colCount = qc.getFieldCount();
			int rowCount = records.size();
			List<HashMap<String,Object>> rs = new ArrayList<HashMap<String,Object>>();
			for(int i = 0;i < rowCount; i ++){
				Object[] r = records.get(i);
				HashMap<String,Object> o = new HashMap<String,Object>();
				rs.add(o);
				for(int j = 0; j < colCount; j ++){
					Object v = r[j];
					
					String name = qc.getFieldName(j);
					o.put(name,v);
					SchemaItem si = sc.item(qc.getFullFieldName(j));
					if(si.isCodedValue()){
						o.put(name + "_text",si.getDisplayValue(v));
					}
				}
			}
			return new QueryResult(totalCount,first,pageSize,hql,cost,rs);
		}
		catch(Exception e){
			throw new QueryDataAccessException(e.getMessage());
		}
	}
	
	private void initQueryContext(QueryContext qc,List queryCnd,String queryCndsType,String sortInfo){
		qc.addEntryName(sc.getId() + " a");
		List cnds = new ArrayList();
		HashMap<String,Boolean> loadedRelation = new HashMap<String,Boolean>();
		List<SchemaItem> items = sc.getAllItemsList();
		String entryName = sc.getId();
		AuthorizeResult r = null;
		for(SchemaItem it:items){
			if(it.isVirtual()){
				continue;
			}
			String fid = it.getId();
			r = user.authorize("storage", entryName + "." + fid);
			if(r.getResult() == AuthorizeResult.NO){
				continue;
			}
			String av = r.getAuthorizeValue();
			if(av.charAt(3) == '0' || it.displayMode() == SchemaItem.DISP_NO_LIST_DATA){
				continue;
			}
			if(it.hasProperty("refAlias")){
				String refAlias = it.getProperty("refAlias");
				qc.addField(fid,refAlias);
				if(loadedRelation.containsKey(refAlias)){
					continue;
				}
				SchemaRelation sr = sc.getRelationByAlias(it.getProperty("refAlias"));
				
				qc.addEntryName(sr.getFullEntryName());
				List cd = sr.getJoinCondition();
				if(cd != null){
					cnds.add(cd);
				}
				loadedRelation.put(refAlias, true);
			}
			else{
				qc.addField(fid,"a");
			}
		}
		if(queryCnd != null){
			cnds.add(queryCnd);
		}
		
		//init where
		AuthorizeResult result = user.authorize("storage",sc.getId());
		if(result.getResult() == AuthorizeResult.NO){
			qc.setWhere("where 1=2");
		}
		else{
			//add role query condition
			if(StringUtils.isEmpty(queryCndsType)){
				queryCndsType = DEFAULT_QUERYCNDSTYPE;
			}
			Condition c = result.getCondition(queryCndsType);
			if(c != null){
				List exp = (List)c.data().get("exp");
				if(exp != null){
					cnds.add(exp);
				}
			}
			//start
			List whereCnd = null;
			int cndCount = cnds.size();
			if(cndCount == 0){
				whereCnd = queryCnd;
			}
			else{
				if(cndCount == 1){
					whereCnd = (List)cnds.get(0);
				}
				else{
					whereCnd = new ArrayList();
					whereCnd.add("and");
					for(Object cd : cnds){
						whereCnd.add((List)cd);
					}
				}
			}
			if(whereCnd != null){
				String where = " where " + ExpRunner.toString(whereCnd,ctx);
				qc.setWhere(where);
			}
		}
		//set sortinfo
		if(StringUtils.isEmpty(sortInfo)){
			sortInfo = sc.getSortInfo();
		}
		qc.setSortInfo(sortInfo);
	}
}

class AuthorizeCode{
	public boolean CAN_CREATE;
	public boolean CAN_UPDATE;
	public boolean CAN_READ;
	public boolean CAN_REMOVE;
	
	public void parse(String acValue){
		CAN_READ = acValue.charAt(3) == '1';
		CAN_CREATE = acValue.charAt(2) == '1';
		CAN_UPDATE = acValue.charAt(1) == '1';
		CAN_REMOVE = acValue.charAt(0) == '1';
	}
	
	public void authorizeAll(){
		CAN_CREATE = CAN_UPDATE = CAN_READ = CAN_REMOVE = true;
	}
	
	public void authorizeNoting(){
		CAN_CREATE = CAN_UPDATE = CAN_READ = CAN_REMOVE = false;
	}
}



